package main

import (
	"bytes"
	"context"
	"errors"
	"fmt"
	"os/exec"
	"strconv"
	"time"

	log "github.com/sirupsen/logrus"
)

// SerialConfig serial port config
// type SerialConfig struct {
// 	Dev      string `json:"dev,omitempty"`
// 	Baudrate int    `json:"baudrate,omitempty"`
// }

// Duration time.Duration alias
type Duration time.Duration

// BeidouSenderConfig beidou config
type BeidouSenderConfig struct {
	// SerialConfig    SerialConfig `json:"serial,omitempty"`
	ToolPath        string   `json:"toolPath,omitempty"`
	ResponseTimeout Duration `json:"responseTimeout,omitempty"`
	Dev             string   `json:"dev,omitempty"`
	Cmd             string   `json:"cmd,omitempty"`
	Addr            string   `json:"addr,omitempty"`
	CommType        string   `json:"commType,omitempty"`
	TransType       string   `json:"transType,omitempty"`
	Interval        float64  `json:"interval,omitempty"`
}

// BeidouResult beidou sending result
type BeidouResult struct {
	Data     string
	Success  bool
	Err      error
	Response []byte
}

type sendItem struct {
	notifyCh chan BeidouResult
	data     string
}

// BeidouSender sender handle
type BeidouSender struct {
	Config  BeidouSenderConfig
	queueCh chan sendItem
	queue   []sendItem
}

// UnmarshalJSON implements json unmarshal interface
func (d *Duration) UnmarshalJSON(raw []byte) error {
	unqoted, err := strconv.Unquote(string(raw))
	if err != nil {
		return err
	}
	xd, err := time.ParseDuration(unqoted)
	if err != nil {
		return err
	}
	*d = Duration(xd)
	return nil
}

// NewBeidouSender create beidou sender instance
func NewBeidouSender(config BeidouSenderConfig) (*BeidouSender, error) {
	sender := &BeidouSender{
		Config:  config,
		queueCh: make(chan sendItem),
		queue:   []sendItem{},
	}
	return sender, nil
}

func (bs *BeidouSender) format(data []byte) string {
	str := fmt.Sprintf("%X", data)
	dev := bs.Config.Dev
	cmd := bs.Config.Cmd
	addr := bs.Config.Addr
	commtype := bs.Config.CommType
	transtype := bs.Config.TransType
	checksumC := fmt.Sprintf("%s%s,%s,%s,%s,%s", dev, cmd, addr, commtype, transtype, str)
	sum := XorCheck([]byte(checksumC))
	return fmt.Sprintf("$%s*%02X\r\n", checksumC, sum)
}

// Send put data into sending queue
func (bs *BeidouSender) Send(data []byte, notifyCh chan BeidouResult) error {
	s := bs.format(data)
	if len(s) <= 0 {
		return errors.New("empty data")
	}
	item := sendItem{
		notifyCh: notifyCh,
		data:     s,
	}
	bs.queueCh <- item
	return nil
}

// func readLine(port *serial.Port, timeout time.Duration) (string, error) {
// 	go func() {
// 		var buf [1024]byte
// 		n, err := port.Read(buf)
// 		if err == nil {

// 		}
// 	}()
// 	t := time.NewTimer(timeout)
// 	select {
// 	case <- t.C:
// 		{
// 			return "", errors.New("timeout")
// 		}
// 	case <-complete:
// 		{
// 			return "", nil
// 		}
// 	}
// }

// func (bs *BeidouSender) writeSentence(data string, ch chan BeidouResult) {
// 	log.WithFields(log.Fields{"data": data}).Info("write data")
// 	c := &serial.Config{
// 		Name:        bs.Config.SerialConfig.Dev,
// 		Baud:        bs.Config.SerialConfig.Baudrate,
// 		ReadTimeout: time.Duration(bs.Config.ResponseTimeout),
// 	}
// 	s, err := serial.OpenPort(c)
// 	if err != nil {
// 		// log.WithFields(log.Fields{"err": err, "COM": c}).Error("error opening device")
// 		ch <- BeidouResult{
// 			Data:    data,
// 			Success: false,
// 			Err:     err,
// 		}
// 		return
// 	}
// 	defer s.Close() // nolint
// 	nw, err := s.Write([]byte(data))
// 	if err != nil {
// 		ch <- BeidouResult{
// 			Data:    data,
// 			Success: false,
// 			Err:     err,
// 		}
// 		return
// 	}
// 	if nw != len(data) {
// 		ch <- BeidouResult{
// 			Data:    data,
// 			Success: false,
// 			Err:     err,
// 		}
// 		return
// 	}

// 	var buf [100]byte
// 	nr, err := s.Read(buf[0:])
// 	if err != nil {
// 		ch <- BeidouResult{
// 			Data:    data,
// 			Success: false,
// 			Err:     err,
// 			// Response: []byte{},
// 		}
// 		return
// 	}
// 	if nr == 0 {
// 		ch <- BeidouResult{
// 			Data:    data,
// 			Success: false,
// 			Err:     err,
// 			// Response: buf,
// 		}
// 		return
// 	}
// 	fmt.Printf("%s\n", string(buf[0:nr]))
// 	ch <- BeidouResult{
// 		Data:     data,
// 		Success:  true,
// 		Err:      err,
// 		Response: buf[0:nr],
// 	}
// }

func (bs *BeidouSender) writeSentence(ctx context.Context, data string, ch chan BeidouResult) {
	path := bs.Config.ToolPath
	cmd := exec.CommandContext(ctx, path, data)
	var stdout bytes.Buffer
	cmd.Stdout = &stdout
	err := cmd.Run()
	ch <- BeidouResult{
		Data:     data,
		Success:  true,
		Err:      err,
		Response: stdout.Bytes(),
	}
}

func (bs *BeidouSender) sendSentence(ctx context.Context, item sendItem) {
	go bs.writeSentence(ctx, item.data, item.notifyCh)
	<-ctx.Done()
}

// Start starts the beidou sending daemon
func (bs *BeidouSender) Start() {
	timer := time.NewTicker(time.Second * time.Duration(bs.Config.Interval))
	var cancel context.CancelFunc
	for {
		select {
		case item := <-bs.queueCh:
			{
				bs.queue = append(bs.queue, item)
				break
			}
		case t := <-timer.C:
			{
				log.WithFields(log.Fields{"timer": t.Format(time.RFC3339), "queue": len(bs.queue)}).Info("beidou sending timer tick")
				if len(bs.queue) > 0 {
					if cancel != nil {
						cancel()
					}
					sendingItem := bs.queue[0]
					bs.queue = bs.queue[1:]
					var ctx context.Context
					ctx, cancel = context.WithDeadline(context.Background(), time.Now().Add(20*time.Second))
					go bs.sendSentence(ctx, sendingItem)
				}
			}
		}
	}
}
